﻿using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace CommonUtils
{
    public static partial class CoreUtil
    {
        public static void CloseSsl()
        => LogUtil.log("请关闭app.UseHttpsRedirection(); ");

        #region IWebHostBuilder IMvcBuilder IFormFile ControllerBase MvcNewtonsoftJsonOptions
        /// <summary>
        /// 解除大文件上传限制
        /// </summary>
        public static IWebHostBuilder CancelSizeLimit(this IWebHostBuilder webHostBuilder)
        {
            webHostBuilder.UseKestrel(options =>
            {
                options.Limits.MaxRequestBodySize = long.MaxValue;
                options.Limits.MaxRequestBufferSize = long.MaxValue;
                options.Limits.MaxRequestLineSize = int.MaxValue;
            });

            //如果没有这句话，IIS模式下.UseKestrel会引发500.3异常
            webHostBuilder.UseIIS();

            return webHostBuilder;
        }

        public static string UsePort(this IWebHostBuilder builder, int port)
        {
            CloseSsl();
            var url = "http://*:" + port;
            builder.UseUrls(url);
            LogUtil.log("UseUrls: " + url);
            return "http://localhost:" + port;
        }

        public static IMvcBuilder SetJson(this IMvcBuilder mvc, bool keepCase = false)
        => mvc.AddNewtonsoftJson(options => options.SetJson(keepCase));

        public static void SaveTo(this IFormFile file, DirectoryInfo folder)
        => FileUtil.Save(folder.Combine(file.FileName), file.OpenReadStream());

        public static void SaveTo(this IFormFile file, string path)
        => file.OpenReadStream().SaveTo(path);

        /// <summary>
        /// 原生的仅支持virtualPath，这个支持绝对路径
        /// </summary>
        public static IActionResult GetFileResult(this ControllerBase controller, string path, string contentType = null)
        {
            if (contentType == null)
                contentType = ContentType.Get(path);
            return controller.File(FileUtil.GetReadStream(path), contentType);
        }

        //原生Json服务中文、变量、注释支持差，这里切换为Newtonsoft提供的Json服务
        //必须引用Microsoft.AspNetCore.Mvc.NewtonsoftJson，引用Newtonsoft.Json此处无效
        public static void SetJson(this MvcNewtonsoftJsonOptions options, bool keepCase = false)
        {
            //保持Json属性/变量大小写 默认是首字母变小写 不保持
            if (keepCase)
                options.SerializerSettings.ContractResolver = new DefaultContractResolver();
            //忽略循环引用
            options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            //设置时间格式
            options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
            //如字段为null值，该字段不会返回到前端
            //options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
        }
        #endregion

        //IServiceCollection中取的数据库单例可不可用？
        //添加/删除可以,如果数据被其他实例修改，缓存与数据库不同步，单例是不知道的，造成数据脏读写
        //sql语句没问题，缓存中数据有问题
        //IApplicationBuilder还未验证，有时初始化会失败，可能紧支持部分环境，比如控制器初始化的时候

        #region IServiceCollection
        public static IServiceCollection Services { get; set; }

        public static IServiceCollection SetServices(this WebApplication app, IServiceCollection services)
        => Services = services;

        public static IServiceCollection GetServices(this WebApplication app)
        => Services;

        public static T Get<T>(this IServiceCollection services)
        {
            if (services == null)
                throw new ExceptionPlus("Services 未赋值");
            return services.BuildServiceProvider().GetService<T>();
        }

        public static T GetFromServices<T>()
        => Services.Get<T>();

        /// <summary>
        /// 解除大文件上传限制
        /// </summary>
        public static void CancelSizeLimit(this IServiceCollection services)
        {
            //此处会导致IIS模式下api.Request.Form获取失败
            if (!ApplicationUtil.IsIIS)
            {
                services.Configure<FormOptions>(options =>
                {
                    options.BufferBodyLengthLimit = long.MaxValue;
                    options.KeyLengthLimit = int.MaxValue;
                    options.MultipartBodyLengthLimit = long.MaxValue;
                    options.MultipartBoundaryLengthLimit = int.MaxValue;
                    options.ValueLengthLimit = int.MaxValue;
                });
            }
        }
        #endregion

        #region IApplicationLifetime
        public static void RegisterStart(this Microsoft.AspNetCore.Hosting.IApplicationLifetime lifeTime, Action callback)
        => lifeTime.ApplicationStarted.Register(callback);

        public static void RegisterStop(this Microsoft.AspNetCore.Hosting.IApplicationLifetime lifeTime, Action callback)
        => lifeTime.ApplicationStopping.Register(callback);

        public static void RegisterStart(this Microsoft.Extensions.Hosting.IApplicationLifetime lifeTime, Action callback)
        => lifeTime.ApplicationStarted.Register(callback);

        public static void RegisterStop(this Microsoft.Extensions.Hosting.IApplicationLifetime lifeTime, Action callback)
        => lifeTime.ApplicationStopping.Register(callback);
        #endregion

        #region WebApplication
        //System.InvalidOperationException:“Cannot resolve scoped service from root provider.”
        //public static T GetService<T>(this WebApplication app)
        //=> app.Services.GetService<T>();

        public static WebApplication UseRazor(this WebApplication app)
        {
            app.UseStaticFiles();
            app.MapRazorPages();
            return app;
        }

        public static void ListenStarted(this WebApplication app, Action action)
        => app.Lifetime.ApplicationStarted.Register(action);

        public static void RegisterStarted(this WebApplication app, Action action)
        => app.Lifetime.ApplicationStarted.Register(action);

        public static void ListenStopped(this WebApplication app, Action action)
        => app.Lifetime.ApplicationStopped.Register(action);

        public static void ListenStopping(this WebApplication app, Action action)
        => app.Lifetime.ApplicationStopping.Register(action);

        public static string[] Urls(this WebApplication app)
        {
            var urls = app.Configuration.GetSection("URLS").Value;
            if (urls != null)
                return urls.Split(';');
            if (app.Urls != null && app.Urls.Count > 0)
                return app.Urls.ToArray();
            var port = app.Configuration.GetSection("HTTPS_PORT").Value;
            if (port != null)
                return ["https://localhost:" + port];
            port = app.Configuration.GetSection("HTTP_PORT").Value;
            if (port != null)
                return ["http://localhost:" + port];
            return ["http://unknown:????"];
        }

        public static string Url(this WebApplication app)
        => app.Urls().FirstOrDefault().Replace("[::]", "localhost");

        public static void OpenHomeWhenStarted(this WebApplication app)
        => app.RegisterStarted(() => ProcessUtil.Open(app.Url()));

        public static void OpenSwaggerWhenStarted(this WebApplication app)
        => app.RegisterStarted(() => app.OpenSwagger());

        public static void SetHomePage(this WebApplication app, string homePage)
        => app.UseDefaultFiles(homePage);

        #endregion

        #region IApplicationBuilder
        //System.InvalidOperationException:“Cannot resolve scoped service from root provider.”
        //public static T GetService<T>(this IApplicationBuilder builder)
        //=> builder.ApplicationServices.GetService<T>();

        /// <summary>
        /// AllowCredentials() 加了这个会报错
        /// WebApplication(:IApplicationBuilder) = WebApplicationBuilder.build();
        /// </summary>
        public static void CrossDomain(this IApplicationBuilder builder)
        => builder.UseCors(corsPolicyBuilder => corsPolicyBuilder.AllowAnyMethod().AllowAnyOrigin().AllowAnyHeader());

        #endregion

        #region WebApplicationBuilder
        public static T GetService<T>(this WebApplicationBuilder builder)
        => builder.Services.Get<T>();

        //cshtml必须放在Pages(pages)下面
        //入口是index.cshtml,该文件必须存在
        //静态文件放在wwwroot下面
        public static IMvcBuilder UseRazor(this WebApplicationBuilder builder)
        => builder.Services.AddRazorPages();

        public static WebApplication UseRazorAndBuild(this WebApplicationBuilder builder)
        {
            builder.UseRazor();
            return builder.Build().UseRazor();
        }

        public static string UsePort(this WebApplicationBuilder builder, int port)
        => builder.WebHost.UsePort(port);

        public static string UseSettingPort(this WebApplicationBuilder builder)
        => builder.UsePort(builder.Configuration.GetValue<int>("port"));

        public static void UseIIS(this WebApplicationBuilder builder)
        {
            //LogUtil.log("如果报错,关闭IIS服务,发布设置勾选删除现有文件");
            builder.WebHost.UseIIS();
        }
        #endregion

        #region ContentTypeProvider
        //app.UseStaticFiles(new StaticFileOptions() { ContentTypeProvider = CoreUtil.ContentTypeProvider });
        public static FileExtensionContentTypeProvider ContentTypeProvider = new FileExtensionContentTypeProvider();

        public static Dictionary<string, string> GetMimeMappings()
        => ContentTypeProvider.Mappings as Dictionary<string, string>;

        private static void FormatExtension(ref string extension)
        {
            extension = extension.SubstringEndByFirstKey('.');
            //填空就给个网页默认值
            if (string.IsNullOrEmpty(extension))
                extension = ".htm";
            extension = '.' + extension.ToLower();
        }

        public static bool IsMimeExist(string extension)
        {
            var map = GetMimeMappings();
            FormatExtension(ref extension);
            return map.ContainsKey(extension);
        }

        public static bool AddMime(string extension)
        {
            var map = GetMimeMappings();
            FormatExtension(ref extension);

            if (map.ContainsKey(extension))
                return true;

            map[extension] = "application/octet-stream";
            return true;
        }

        public static void AddMimesByFolder(string folder)
        {
            var map = GetMimeMappings();

            var extensions = FolderUtil.GetFiles(folder).Select(m => m.Extension).Distinct().ToArray();
            foreach (var extension in extensions)
            {
                if (map.ContainsKey(extension))
                    continue;
                map[extension] = "application/octet-stream";
            }
        }

        public static bool RemoveMime(string extension)
        {
            var map = GetMimeMappings();
            FormatExtension(ref extension);

            if (!map.ContainsKey(extension))
                return true;

            map.Remove(extension);
            return true;
        }
        #endregion

    }
}
